import type { AObject } from "./AObject";
import type { Agent } from "./Agent";
import type { AVerb, CommandConfig } from "./CommandParser";
import * as Debug from "./Debug";
import { ActionReportType, RoutineResult, RoutineResultType, type ActionReportArguments, type ScheduledRoutine } from "./Definitions";
import type { HistoryEntry } from "./NarrativeBase";
import type { NarrativeContext } from "./NarrativeContext";
import type { NLPError } from "./Selector";
import { U } from "./Util";
import { World, type ScheduleInterruptRequest, type ScheduledProcess, type TypedScheduledProcess } from "./WorldBase";

export class Character {
    constructor(public agent: Agent) {
        agent.character = this;
    }

    attentionLevel = 1;
    ignoreLevel = 1;

    /**
     * @returns `true` to stop current action
     */
    *onInterrupt(level: number): TypedScheduledProcess<boolean> {
        return level >= this.ignoreLevel;
    }
    onTurn = (): ScheduledProcess | null => null;

    reportFailState(r: RoutineResult): HistoryEntry | null {
        return U.metaText(this.agent, `[fail ${RoutineResultType[r.type]}]`);
    }

    reportAction(a: ActionReportArguments): HistoryEntry {
        return U.metaText(this.agent, `[action ${ActionReportType[a.type]}]`)
    }

    *describeZone(): ScheduledRoutine {
        U.metaText(this.agent, `[describeZone]`);
        return RoutineResult.Ok();
    }

    *describeObject(obj: AObject[]): ScheduledRoutine {
        U.metaText(this.agent, `[describeObject ${obj.map((x) => x.name.value)}]`);
        return RoutineResult.Ok();
    }

    *readObject(obj: AObject): ScheduledRoutine {
        U.metaText(this.agent, `[readObject ${obj.name.value}]`);
        return RoutineResult.Ok();
    }
    
    protected readonly defaultConfig: CommandConfig = {
        character: this,
        defaultRange: () => [
            ...(this.agent.holdingItem ? [this.agent.holdingItem] : []),
            ...this.agent.inventory, 
            ...this.agent.parentZone.extendedObjects()],
        notFoundMsg: (orig, name) => U.metaText(this.agent, 
            `[notFound ${name}, ${orig}]`),
        defaultsToMsg: (objs) => U.metaText(this.agent, 
            `[defaultsTo ${objs.map((x) => x.name.value).join(', ')}]`),
        conditionNotMetMsg: (obj) => U.metaText(this.agent, 
            `[conditionNotMetDefault ${obj.name.value}]`),
        reportError: (err) => U.metaText(this.agent, 
            `[NLP error: ${(err as NLPError).expr} : ${err}]`),
    };

    protected config: CommandConfig = this.defaultConfig;
    protected actions: AVerb[] = [];

    *parseCommand(cmd: string, context: NarrativeContext): ScheduledProcess {
        for (let x in this.actions) {
            let action = this.actions[x as any]!;
            let args = yield* action.parseArguments(cmd, this.config, context);
            if (!args) continue;
            if (args === true) return;
            yield* action.exec(args, this.config);
            return;
        }
        U.metaText(this.agent, '[指令解析失败：请尝试换一种说法]').submitMomentary();
    }    

    *autoPassTime(t: number, checker?: (int: ScheduleInterruptRequest) => boolean)
        : TypedScheduledProcess<boolean> 
    {
        Debug.assert(t > 0);
        let target = World.schedule.absoluteTime + t;
        while (true) {
            let result = yield World.schedule.requireTime(
                target - World.schedule.absoluteTime, checker);
            if (result.success) return true;
            if (checker || (yield* this.onInterrupt(result.interrupt!.severity)))
                return false;
        }
    }

    protected *healthCoroutine(): ScheduledProcess {
        throw new Error('Not implemented');
    }

    *disambiguateResults(expression: string, candidates: AObject[])
        : TypedScheduledProcess<AObject | null>
    {
        throw new Error('Not implemented');
    }

    showStats() {
        throw new Error('Not implemented');
    }

    *wait(time: number): ScheduledProcess {
        U.metaText(this.agent, `[wait ${time}]`);
        yield* this.autoPassTime(time);
        return;
    }
}